#include "Col.ph"
#ifdef __GNUG__
#pragma implementation
#endif

#include "GapText.h"

#include "Class.h"
#include "RegularExp.h"
#include "Error.h"
#include "Math.h"
#include "String.h"
#include "TextMarks.h"
#include "TextFormatter.h"

const int
	cShrinkFactor = 2,
	cMaxOutput    = 500;

//---- data structure ----------------------------------------------------------

	 //@Pict:gaptext.pic 

//---- GapText -----------------------------------------------------------------

NewMetaImpl(GapText,Text, (T(size), T(length), T(part1len), T(gaplen),
				TV(body, part1len), TV(body2, part2len)));

GapText::GapText(int cap, Font *fd)
{
	GapTextInit(0, 0, fd, cap);
}

GapText::GapText(const byte *buf, int len, Font *fd)
{
	GapTextInit((char*)buf, len, fd, cTextInitCap);
}

GapText::GapText(const char *buf, int len, Font *fd)
{
	GapTextInit(buf, len, fd, cTextInitCap);
}

GapText::~GapText()
{
	SafeDelete(body);
	part2body= body2= 0;
}

void GapText::GapTextInit(const char *buf, int l, Font *fd, int cap)
{
	if (cap < cTextInitCap)
		cap= cTextInitCap;
	if (l < 0)
		l= buf ? strlen(buf) : 0;
	size= Math::Max(cap, l);
	body= new byte[size];
	if (buf)
		MemCpy(body, buf, size);
	Init(l, fd);
}

void GapText::Init(int l, Font *fd)
{
	length= l;
	Update(l);
	charStyle= new_CharStyle(fd);
	SetDefTab(fd->Width(' ') * 8);
	Terminate();
}

void GapText::InitNew()
{
	Text::InitNew();
	GapTextInit(0, 0, gSysFont, cTextInitCap);
	//Init(0, gSysFont);
}

void GapText::Terminate()
{
	if (!IsTerminated())
		AddChar(Size(), '\0');
}

void GapText::AddChar(int at, byte b)
{
	if (HighWaterMark(1))
		Expand(GrowSize(size+1), Size());
	else
		MoveGap(at);
	body[at]= b;
	length+= 1;
	Update(part1len+1);
}

void GapText::ReplaceRange(int from, int to, Text *tsrc, int sfrom, int sto)
{
	GapText *src;
	byte *buf= 0;

	if (!CheckRange(End(), from, to))
		Error("ReplaceRange", "out of range");

	if (!tsrc->IsKindOf(GapText)) {  // convert the text into a GapText
		int s= sto-sfrom;
		buf= new byte[s+1];
		tsrc->CopyInStr(buf, s+1, sfrom, sto);
		src= new GapText(buf, s);
		sto= sto-sfrom;
		sfrom= 0;
	} else
		src= (GapText *)tsrc;
		
	int shift= (sto - sfrom) - (to - from);
	if (HighWaterMark(shift))
		Expand(GrowSize(size+shift), from);
	else
		MoveGap(from);

	src->CopyTo(body+from, sfrom, sto);

	length+= shift;
	Update(part1len + (sto-sfrom));

	if (LowWaterMark())
		Shrink();
	if (buf) {
		delete src;
		delete buf;
	}
}

void GapText::CopyInStr(byte *str, int strsize, int from, int to)
{
	if (!CheckRange(length, from, to))
		Error("CopyInStr", "out of range");
	to= Math::Min(to, from + strsize-1);
	CopyTo(str, from, to);
	str[to-from]= '\0';
	//fprintf(stderr,"GapText::CopyInStr(<%s>,%d,%d,%d)\n",str,strsize,from,to);
}

Text *GapText::MakeScratchText(byte *buf, int len)
{
	if (buf)
		return new GapText(buf, len);
	return new GapText(len);
}

int GapText::Search(RegularExp *rex, int *nMatched, int start, int range, bool dir)
{
	if (dir == cSearchForward)
		return rex->SearchForward2((char*)body, part1len,
			(char*)(body+part1len+gaplen),
				length-part1len, start, range, 0, nMatched);

	// BUG in RegularExp::SearchBackward2 ??
	MoveGap(length);
	return rex->SearchBackward((char*)body, nMatched, start, length, range, 0);
}

byte *GapText::GetLineAccess(byte **buf, int from, int to)
{
	if (!CheckRange(length, from, to))
		return 0;

	if ((part1len < to) && (part1len >= from)) {
		// Gap is in between
		*buf= (byte*) Storage::ReAlloc(*buf, to-from + 20);
		for (int i= from; i < to; i++)
			(*buf)[i-from]= CharAt(i);
		return *buf;
	}
	if (part1len < from)
		return &part2body[from];
	return &body[from];
}

byte &GapText::operator[](int i)
{
	if (i >= length)
		i= length-1;
	if (i < part1len)
		return body[i];
	return part2body[i];
}

int GapText::Size()
{
	return length;
}

TextIter *GapText::MakeIterator(int from, int to, void *placement)
{
	return new(placement) GapTextIter(this, from, to);
}

const char *GapText::AsString()
{
	MoveGap(length);
	return (const char*) body;
}

OStream &GapText::PrintOn(OStream &s)
{
	Text::PrintOn(s);
	MoveGap(length);
	return s.PrintString(body, Size());
}

IStream &GapText::ReadFrom(IStream &s)
{
	Text::ReadFrom(s);
	SafeDelete(body);
	s.ReadString(&body, &length);
	size= length;
	Update(length);
	return s;
}

OStream &GapText::PrintOnAsPureText(OStream &s)
{
	s.write(body, part1len);
	s.write(body2, End() - part1len);
	return s;
}

IStream &GapText::ReadFromAsPureText(IStream &s, long sizeHint)
{
	char ch;

	if (sizeHint > 0 && ((int)(sizeHint - size)) > 0)
		Expand((int)sizeHint + 50);
	if (body == 0)
		body= new byte[size= 1024];
	length= 0;

	while (s.get(ch)) {
		if (length >= size){
			part2body= body ;
			body= new byte[size= (size+1)*2];
			MemCpy(body, part2body, length);
			delete part2body;
		}
		body[length++]= ch;
	}

	Update(length);
	Terminate();
	if (!s.eof())
		Error("ReadFromAsPureText", "missing EOF");
	return s;
}

//---- private -----------------------------------------------------------------

void GapText::Update(int l)
{
	part1len= l;
	gaplen= size - length;
	part2body= body + gaplen;
	part2len= length - part1len;
	body2= part2body + part1len;
}

void GapText::MoveGap(int to)
{
	if (to == part1len)
		return;
	if (part1len > to)
		MemCpy(body+to+gaplen, body+to, part1len-to);
	else
		MemCpy(body+part1len, body+part1len+gaplen, to-part1len);
	part1len= to;
}

void GapText::CopyTo(byte *dst, int from, int to)
{
	// Copy Text between 'from' and 'to' to 'dst'
	int beforegap= Math::Max(0, part1len-from) - Math::Max(0, part1len-to);
	int aftergap=  Math::Max(0, to-part1len)   - Math::Max(0, from-part1len);

	if (beforegap)
		MemCpy(dst, body+from, beforegap);
	if (aftergap)
		MemCpy(dst+beforegap, part2body+Math::Max(part1len, from), aftergap);
}

void GapText::Expand(int to, int moveto)        // Expand size of Text to 'to'
{
	if (to < size)
		return;

	if (moveto > length)
		moveto= length;

	size= to;
	byte *pos= new byte[size];
	byte *part2= pos+size-length;

	if (moveto < part1len) {
		MemCpy(pos, body, moveto);
		MemCpy(part2+moveto, body+moveto, part1len-moveto);
		MemCpy(part2+part1len, body2, length-part1len);
	} else {
		MemCpy(pos, body, part1len);
		MemCpy(pos+part1len, body2, moveto-part1len);
		MemCpy(part2+moveto, part2body+moveto, length-moveto);
	}
	delete body;
	body= pos;
	Update(moveto);
}

void GapText::Shrink(int to)
{
	if (to == 0 || to < length)
		to= (size/cShrinkFactor) + 1;

	if (to > size || to < length)
		return;

	size= to;
	byte *pos= new byte[size];
	MemCpy(pos, body, part1len);
	MemCpy(pos+part1len, body+part1len+gaplen, part2len);
	delete body;
	body= pos;
	Update(length);
}

//---- GapTextIter -------------------------------------------------------------

GapTextIter::GapTextIter(Text *s, int from, int to) : TextIter(s, from, to)
{
	if (!s->IsKindOf(GapText))
		Error("GapTextIter::Error", "GapText expected (%s received)", s->ClassName());
	st= (GapText*)s;

	escape= st->GetMarkChar();
	nextFontChange= st->GetNextFontChange(ce, sp);
	if (sp == 0)
		Error("GapTextIter::GapTextIter", "style is nil");
}

void GapTextIter::Reset(Text *s, int from, int to)
{
	if (!s->IsKindOf(GapText))
		Error("GapTextIter::Reset", "GapTextIter expected (%s received)",
																s->ClassName());
	st= (GapText*)s;

	TextIter::Reset(s, from, to);
	nextFontChange= st->GetNextFontChange(ce, sp);
	escape= st->GetMarkChar();
	if (sp == 0)
		Error("GapTextIter::Reset", "style is nil");
}

int GapTextIter::GraphicSize(int at, LineDesc *l)
{
	VisualMark *vmp= st->GetVisualMarkAt(at);
	if (l)
		l->Max(vmp->Base(), vmp->GetExtent().y);
	return vmp->GetExtent().x;
}

int GapTextIter::operator()(int *w, LineDesc *ld)
{
	unget= ce;
	if (ce == upto )
		return cEOT;

	TestFontChange(sp);
	int ch= CharAt(ce);
	if (TestVisualMark(ce, ch)) {
		if (w)
			*w= GraphicSize(ce, ld);
	}
	else {
		if (w)
			*w= sp->GetFont()->Width(ch);
		if (ld)
			ld->FromFont(sp->GetFont());
	}
	ce++;
	return ch;
}

int GapTextIter::Token(int *w, LineDesc *ld)
{
	unget= ce;
	*w= 0;
	TestFontChange(sp);
	if (ld)
		ld->FromFont(sp->GetFont());
	if (ce >= upto)
		return cEOT;
	register int ch= CharAt(ce);

	if (TestVisualMark(ce ,ch)) {
		*w= GraphicSize(ce, ld);
		ce++;
		return ch;
	}

	if (Iswordwrap(ch)) {
		*w= sp->GetFont()->Width(ch);
		ce++;
		return ch;
	}
	while (ce < upto && !Iswordwrap(CharAt(ce)) && !TestVisualMark(ce, CharAt(ce))) {
		if (TestFontChange(sp) && ld)
			ld->Max(sp->GetFont());
		ch= CharAt(ce++);
		*w+= sp->GetFont()->Width(ch);
	}
	return ch;
}

